/******************************************************************************************

 Made by Flashphoner. All rights reserved.
 These sources can only be used for working with Flashphoner server.
 If you want use this to create your own application (separate from Flashphoner server)
 please contact support@flashphoner.com

 ******************************************************************************************/

package com.flashphoner.app;

import com.flashphoner.sdk.rtmp.Config;
import com.flashphoner.sdk.rtmp.IRtmpClient;
import com.flashphoner.sdk.softphone.ErrorCodes;
import com.flashphoner.sdk.softphone.ISoftphone;
import com.flashphoner.sdk.softphone.ISoftphoneCall;
import com.flashphoner.sdk.softphone.Logger;
import com.flashphoner.sdk.softphone.exception.PortsBusyException;
import com.flashphoner.sdk.softphone.exception.CrossCallException;
import com.flashphoner.sdk.softphone.exception.SoftphoneException;
import com.wowza.wms.module.ModuleBase;
import com.wowza.wms.module.IModuleOnConnect;
import com.wowza.wms.module.IModuleOnApp;
import com.wowza.wms.client.IClient;
import com.wowza.wms.request.RequestFunction;
import com.wowza.wms.amf.AMFDataList;
import com.wowza.wms.amf.AMFDataObj;
import com.wowza.wms.application.IApplicationInstance;

import com.flashphoner.sdk.rtmp.IRtmpClientsCollection;
import com.flashphoner.sdk.rtmp.IRtmpClientsCollectionSupport;

import java.util.Map;
import java.util.HashMap;
import java.util.Properties;

public class FlashphonerApp extends ModuleBase implements IModuleOnConnect, IModuleOnApp, IRtmpClientsCollectionSupport {

    // SIP accounts pool, one telephone for every user pattern
    private static Map<String, SipAccount> sipAccountsPool;

    public static final String APPLICATION_NAME = "flashphoner_app";
    public static IApplicationInstance instance;

    public void onAppStart(IApplicationInstance appInst) {
        instance = appInst;
        if (sipAccountsPool == null) {
            getLogger().info("Initializing sip accounts...");
            sipAccountsPool = new HashMap<String, SipAccount>();
            Properties props = Config.getInstance().getProperties();
            for (Object objectPropertyName : props.keySet()) {
                String nameProperty = (String) objectPropertyName;
                if (nameProperty.matches("callbox.*")) {
                    String sipAccountString = props.getProperty(nameProperty);
                    if (sipAccountString == null || "".equals(sipAccountString)) {
                        continue;
                    }
                    getLogger().info("Init sip account string:" + sipAccountString);
                    String[] sipAccountStrings = sipAccountString.split(",");
                    if (sipAccountStrings.length != 3) {
                        continue;
                    }
                    int port = 5060;
                    String[] sipServerStrings = sipAccountStrings[0].split(":");
                    if (sipServerStrings.length > 1) {
                        port = Integer.parseInt(sipServerStrings[1]);
                    }

                    sipAccountsPool.put(sipAccountStrings[1], new SipAccount(sipAccountStrings[1], sipAccountStrings[2], sipServerStrings[0], port, SipAccount.FREE));
                    getLogger().info("Init sip account sipLogin=" + sipAccountStrings[1] + " sipPassword=" + sipAccountStrings[2] + " free=" + SipAccount.FREE);
                }
            }
            getLogger().info("Initializing sip accounts complete");
        }
    }

    public void onAppStop(IApplicationInstance appInst) {

    }

    public void onConnect(IClient client, RequestFunction requestFunction, AMFDataList params) {

        getLogger().info("FlashphonerApp.onConnect() " + params);

        if (!isDefaultInstance(client)) {
            client.acceptConnection();
            return;
        }

        AMFDataObj obj = params.getObject(PARAM1);

        if (obj == null) {
            Logger.logger.info("Connect's parameters are NULL");
            return;
        }

        String mode = obj.getString("mode");
        if (mode == null) {
            mode = Config.MODE_FLASHPHONER;
        }

        String visibleName = obj.getString("visibleName");
        assert visibleName != null;

        boolean regRequired = obj.getBoolean("registerRequired");

        IRtmpClient rtmpClient = null;
        if (mode.equals(Config.MODE_FLASHPHONER)) {
            String login = obj.getString("login");
            assert login != null;

            String password = obj.getString("password");
            assert password != null;

            String outboundProxy = Config.getInstance().getProperty("outbound_proxy");
            String sipProviderAddress;
            if (outboundProxy == null || "".equals(outboundProxy)) {
                sipProviderAddress = obj.getString("sipProviderAddress");
            } else {
                sipProviderAddress = outboundProxy;
            }
            if (sipProviderAddress == null || "".equals(sipProviderAddress)) {
                client.rejectConnection();
                return;
            }

            int sipProviderPort = 5060;
            try {
                sipProviderPort = Integer.parseInt(obj.getString("sipProviderPort"));
            } catch (NumberFormatException ex) {
            }
            assert sipProviderPort != 0;
            Logger.logger.info("sipProviderAddress - " + sipProviderAddress);
            Logger.logger.info("outboundProxy - " + outboundProxy);
            rtmpClient = new RtmpClient(login, password, client, sipProviderAddress, sipProviderPort, visibleName, regRequired, APPLICATION_NAME, Config.MODE_FLASHPHONER);

        } else {
            for (SipAccount sipAccount : sipAccountsPool.values()) {
                if (sipAccount.isFree()) {
                    rtmpClient = new RtmpClient(sipAccount.getSipLogin(), sipAccount.getSipPassword(), client, sipAccount.getSipServer(), sipAccount.getSipPort(), visibleName, regRequired, APPLICATION_NAME, Config.MODE_CLICK2CALL);
                    sipAccount.setFree(false);
                    break;
                }
            }
            if (rtmpClient == null) {
                client.rejectConnection();
                return;
            }
        }
        AMFDataObj amfDataObj = new AMFDataObj();
        amfDataObj.put("login", rtmpClient.getLogin());
        amfDataObj.put("password", rtmpClient.getPassword());
        amfDataObj.put("sipProviderAddress", rtmpClient.getSipProviderAddress());
        amfDataObj.put("sipProviderPort", rtmpClient.getSipProviderPort());
        client.call("getUserData", null, amfDataObj);

        getRtmpClients().add(rtmpClient);

        getLogger().info("FlashphonerApp.getRtmpClients() " + getRtmpClients());

        client.acceptConnection();

        client.call("getVersion", null, Config.getInstance().getVersion());

    }

    public void onDisconnect(IClient client) {
        Logger.logger.info("Disconnect client: " + client.getClientId());
        if (!isDefaultInstance(client)) {
            return;
        }

        IRtmpClient rtmpClientByClient = getRtmpClients().findByClient(client);
        if (rtmpClientByClient != null) {
            Logger.logger.info("Shutdown RtmpClient: " + rtmpClientByClient.getLogin());
            IRtmpClient rtmpClient = getRtmpClients().remove(rtmpClientByClient);
            if (rtmpClient.getMode().equals(Config.MODE_CLICK2CALL)) {
                SipAccount sipAccount = sipAccountsPool.get(rtmpClient.getLogin());
                if (sipAccount != null) {
                    sipAccount.setFree(SipAccount.FREE);
                }
            }
            ISoftphone softphone = rtmpClient.getSoftphone();
            if (softphone != null) {
                try {
                    softphone.release();
                } catch (Exception e) {
                    Logger.logger.error("Can not release softphone! Possible leak", e);
                }
            }
            //release sip account in pool
            getLogger().info("Release sip account in pool: " + rtmpClient.getLogin());
        }
    }

    private boolean isDefaultInstance(IClient client) {
        getLogger().info("checking instance " + client.getAppInstance().getName());
        return client.getAppInstance().getName().equals("_definst_");
    }

    public void onConnectAccept(IClient iClient) {

    }

    public void onConnectReject(IClient iClient) {

    }

    public IRtmpClientsCollection getRtmpClients() {
        return RtmpClients.getInstance();
    }

    public void sendDtmf(IClient client, RequestFunction requestFunction, AMFDataList params) {
        String dtmf = params.getString(PARAM1);
        String callId = params.getString(PARAM2);
        getLogger().info("Send DTMF: " + dtmf);
        if ((dtmf != null) && (dtmf.length() != 0)) {
            IRtmpClient rtmpClient = getRtmpClients().findByClient(client);
            try {
                rtmpClient.sendDtmf(callId, dtmf);
            } catch (SoftphoneException e) {
                Logger.logger.error("Can not send DTMF", e);
            }
        }
    }

    public void call(IClient client, RequestFunction requestFunction, AMFDataList params) {
        getLogger().info("call " + params);
        IRtmpClient rtmpClient = getRtmpClients().findByClient(client);

        String caller = rtmpClient.getLogin();
        String visibleName = params.getString(PARAM1);
        Boolean isVideoCall = params.getBoolean(PARAM1);

        String callee;
        if (rtmpClient.getMode().equals(Config.MODE_CLICK2CALL)) {
            callee = Config.getInstance().getProperty("callee");
        } else {
            callee = params.getString(PARAM2);
        }

        if ((caller == null) || (caller.length() == 0)) {
            caller = rtmpClient.getLogin();
        }
        if (callee == null || "".equals(callee)) {
            rtmpClient.fail(ErrorCodes.USER_NOT_AVAILABLE, null);
            return;
        }

        if ((visibleName == null) || (visibleName.length() == 0)) {
            visibleName = rtmpClient.getLogin();
        }

        ISoftphoneCall call;

        try {

            call = rtmpClient.call(caller, callee, visibleName, isVideoCall);

        } catch (PortsBusyException e) {
            rtmpClient.fail(ErrorCodes.MEDIA_PORTS_BUSY, null);
            return;
        } catch (SoftphoneException e) {
            Throwable cause = e.getCause();
            if ((cause != null) && (cause instanceof CrossCallException)) {
                Logger.logger.info("Cross call has been detected caller=" + caller + " callee=" + callee);
            } else {
                Logger.logger.error("Softphone error", e);
            }
            return;
        }

        ModuleBase.sendResult(client, params, call.toAMFDataObj());
    }

    public void answer(IClient client, RequestFunction requestFunction, AMFDataList params) {
        getLogger().info("answer " + params);
        String callId = params.getString(PARAM1);
        Boolean isVideoCall = params.getBoolean(PARAM2);
        IRtmpClient rtmpClient = getRtmpClients().findByClient(client);
        try {
            rtmpClient.answer(callId, isVideoCall);
        } catch (SoftphoneException e) {
            Logger.logger.error("Can not answer the call", e);
        }
    }

    public void updateCallToVideo(IClient client, RequestFunction requestFunction, AMFDataList params) {
        getLogger().info("updateCallToVideo " + params);
        String callId = params.getString(PARAM1);
        IRtmpClient rtmpClient = getRtmpClients().findByClient(client);
        try {
            rtmpClient.updateCallToVideo(callId);
        } catch (SoftphoneException e) {
            Logger.logger.error("Can not update call to video", e);
        }
    }

    public void transfer(IClient client, RequestFunction requestFunction, AMFDataList params) {
        getLogger().info("transfer " + params);
        String callId = params.getString(PARAM1);
        String callee = params.getString(PARAM2);
        IRtmpClient rtmpClient = getRtmpClients().findByClient(client);
        try {
            rtmpClient.transfer(callId, callee);
        } catch (SoftphoneException e) {
            Logger.logger.error("Can not transfer call", e);
        }
    }

    public void hold(IClient client, RequestFunction requestFunction, AMFDataList params) {
        getLogger().info("hold " + params);
        String callId = params.getString(PARAM1);
        Boolean isHold = params.getBoolean(PARAM2);
        IRtmpClient rtmpClient = getRtmpClients().findByClient(client);
        try {
            rtmpClient.hold(callId, isHold);
        } catch (SoftphoneException e) {
            Logger.logger.error("Can not hold call", e);
        }
    }

    public void hangup(IClient client, RequestFunction requestFunction, AMFDataList params) {
        getLogger().info("hangup " + params);
        String callId = params.getString(PARAM1);
        IRtmpClient rtmpClient = getRtmpClients().findByClient(client);
        try {
            rtmpClient.hangup(callId);
        } catch (SoftphoneException e) {
            Logger.logger.error("Can not hangup call", e);
        }
    }

}
